package com.lk.security.config;

import com.lk.security.authentication.code.ImageCodeValidateFilter;
import com.lk.security.authentication.mobile.MobileAuthenticationConfig;
import com.lk.security.authentication.mobile.MobileValidateFilter;
import com.lk.security.authentication.session.CustomLogoutHandler;
import com.lk.security.authorize.AuthorizeConfigurerManager;
import com.lk.security.properties.AuthenticationProperties;
import com.lk.security.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

import javax.sql.DataSource;

/**
 * @author Aspirin 注解：EnableWebSecurity - 开启spring-security过滤链 filter
 *     注解EnableGlobalMethodSecurity:开启注解方法级别权限控制
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
  @Autowired private UserDetailsService customUserDetailsService;
  @Autowired private SecurityProperties securityProperties;
  @Autowired private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
  @Autowired private AuthenticationFailureHandler customAuthenticationFailureHandler;
  @Autowired private ImageCodeValidateFilter imageCodeValidateFilter;
  @Autowired private DataSource dataSource;
  /** 校验手机验证码 */
  @Autowired private MobileValidateFilter mobileValidateFilter;
  /** 校验在手机号是否存在,就是手机号认证 */
  @Autowired private MobileAuthenticationConfig mobileAuthenticationConfig;

  @Autowired private InvalidSessionStrategy invalidSessionStrategy;
  /** 当同个用户session数量超过指定值之后 ,会调用这个实现类 */
  @Autowired private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
  /** 退出 清除缓存 */
  @Autowired private CustomLogoutHandler customLogoutHandler;

  @Bean
  public PasswordEncoder passwordEncoder() {
    // 明文+随机盐值》加密存储
    return new BCryptPasswordEncoder();
  }

  /**
   * 认证管理器： 1. 认证信息（用户名，密码）
   *
   * @param auth auth
   * @throws Exception Exception
   */
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // 数据库存储的密码必须是加密后的，不然会报错：There is no PasswordEncoder mapped for the id "null"
    auth.userDetailsService(customUserDetailsService);
  }

  /**
   * 记住我功能 是否启动项目时自动创建表，true自创建 jdbcTokenRepository.setCreateTableOnStartup(true);
   *
   * @return JdbcTokenRepositoryImpl
   */
  @Bean
  public JdbcTokenRepositoryImpl jdbcTokenRepository() {
    JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
    jdbcTokenRepository.setDataSource(dataSource);
    // 是否启动项目时自动创建表，true自创建
    return jdbcTokenRepository;
  }

  @Autowired private AuthorizeConfigurerManager authorizeConfigurerManager;

  /**
   * /** 当你认证成功之后 ，spring security它会重写向到你上一次请求上 资源权限配置： 1. 被拦截的资源
   *
   * @param http http
   * @throws Exception Exception
   */
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    AuthenticationProperties authentication = securityProperties.getAuthentication();
    //        http.httpBasic() // 采用 httpBasic认证方式
    // 表单登录方式  addFilterBefore 用户密码之前拦截器
    http.addFilterBefore(mobileValidateFilter, UsernamePasswordAuthenticationFilter.class)
        .addFilterBefore(imageCodeValidateFilter, UsernamePasswordAuthenticationFilter.class)
        .formLogin()
        .loginPage(authentication.getLoginPage())
        // 登录表单提交处理url, 默认是/login
        .loginProcessingUrl(authentication.getLoginProcessingUrl())
        // 默认的是 username
        .usernameParameter(authentication.getUsernameParameter())
        // 默认的是 password
        .passwordParameter(authentication.getPasswordParameter())
        .successHandler(customAuthenticationSuccessHandler)
        .failureHandler(customAuthenticationFailureHandler)
        // .and()
        // .authorizeRequests() // 认证请求
        // 放行/login/page不需要认证可访问
        //        .antMatchers(
        //            authentication.getLoginPage(),
        //            authentication.getImageCodeUrl(),
        //            authentication.getMobilePag(),
        //            authentication.getMobileCodeUrl())
        //        .permitAll()
        // .anyRequest()
        // .authenticated() // 所有访问该应用的http请求都要通过身份认证才可以访问
        .and()
        .rememberMe()
        .tokenRepository(jdbcTokenRepository())
        .tokenValiditySeconds(authentication.getTokenValiditySeconds())
        .and()
        // session管理
        .sessionManagement()
        // 当session失效后的处理类
        .invalidSessionStrategy(invalidSessionStrategy)
        // 每个用户在系统中最多可以有多少个session
        .maximumSessions(1)
        // 当用户达到最大session数后，则调用此处的实现
        .expiredSessionStrategy(sessionInformationExpiredStrategy)
        // 当一个用户达到最大session数,则不允许后面再登录
        // 【建议不开启，有较大安全隐患，开启后remember-me功能会失效】
        // .maxSessionsPreventsLogin(true)
        .sessionRegistry(sessionRegistry())
        .and()
        .and()
        .logout()
        // 退出 清除缓存
        .addLogoutHandler(customLogoutHandler)
        // 自定义退出url
        .logoutUrl("/user/logout")
        // 退出成功后跳转地址
        .logoutSuccessUrl("/mobile/page")
        // 退出后删除什么cookie值(删除JSESSIONID后就不会走Session失效处理类了)
        .deleteCookies("JSESSIONID");

    // 关闭跨站请求伪造-这样就可以用get请求使用logout
    http.csrf().disable();
    // 将手机认证添加到过滤器链上
    http.apply(mobileAuthenticationConfig);

    // 将所有的授权配置统一的起来
    authorizeConfigurerManager.configure(http.authorizeRequests());
  }

  /**
   * 为了解决退出重新登录问题
   *
   * @return SessionRegistry实现类
   */
  @Bean
  public SessionRegistry sessionRegistry() {
    return new SessionRegistryImpl();
  }

  /**
   * 一般是针对静态资源放行
   *
   * @param web web
   */
  @Override
  public void configure(WebSecurity web) {
    web.ignoring().antMatchers(securityProperties.getAuthentication().getStaticPaths());
  }
}
